/**
 * 控制模块
 * 控制数据交互及网页渲染
 */
#pragma once

#include<iostream>
#include<string>
#include<mutex>
#include<atomic>
#include<fstream>
#include<jsoncpp/json/json.h>
#include"View.hpp"
#include"Model.hpp"
//#include"Model_sql.hpp"
#include"../Comm/httplib.h"


namespace OnlineOJ
{
    namespace ns_Control
    {
        //提供服务的主机
        class Machine
        {
        public:
            Machine()
                :_ip(""),_port(0),_load(0)
            {}
            void SetIP(const std::string& ip)
            {
                _ip=ip;
            }
            void SetPort(int port)
            {
                _port=port;
            }
            void SetLoad(const int& num)
            {
                _load=num;
            }
            std::string GetIP()
            {
                return _ip;
            }
            int GetPort()
            {
                return _port;
            }
            uint64_t GetLoad()
            {
                return _load;
            }
            //负载增加
            void LoadIncrease()
            {
                _load++;
            }
            //负载减少
            void LoadDecrease()
            {
                _load--;
            }
        private:
            std::string _ip;//主机IP
            int _port;//端口号
            std::atomic<uint64_t> _load;//负载数量，使用atomic保证线程安全
        };

        using MachinePtr=std::shared_ptr<Machine>;
        const std::string service_machine_path="./conf/service_machine.conf";
        //负载均衡模块
        class LoadBlance
        {
            //加载存储主机信息的配置文件
            bool LoadConf(const std::string& path=service_machine_path)
            {
                std::ifstream in(path);
                if(!in.is_open())
                {
                    LOG(FATAL,"加载%s配置文件失败！\n",path.c_str());
                    return false;
                }
                //读取文件信息
                std::string line;
                while(std::getline(in,line))
                {
                    std::vector<std::string> result;
                    int ret=ns_Detail::StringUtil::SplitString(line,result,":");
                    if(ret!=2)
                    {
                        LOG(WARNING,"%s切分失败！\n",line.c_str());
                        continue;
                    }
                    //构建主机对象
                    auto mp=std::make_shared<Machine>();
                    mp->SetIP(result[0]);
                    mp->SetPort(std::stoi(result[1]));
                    //维护主机信息(默认直接上线提供服务)
                    _online.push_back(_machines.size());
                    _machines.push_back(mp);
                }
                in.close();
                LOG(INFO,"主机信息加载成功！\n");
                return true;
            }
        public:
            LoadBlance()
            {
                assert(LoadConf());
            }
            
            //智能选择主机
            //id：最终选择的主机id
            //mp：最终选择的主机信息
            bool SmartChoice(int& id,MachinePtr& mp)
            {
                std::unique_lock<std::mutex> lock(_mutex);
                
                int online_num=_online.size();
                if(online_num==0)
                {
                    LOG(FATAL,"没有可提供服务的主机！\n");
                    return false;
                }

                //遍历所有主机，找到负载最小的
                id=_online[0];
                mp=_machines[id];
                uint64_t min_load=mp->GetLoad();
                for(int i=1;i<_online.size();i++)
                {
                    uint64_t cur_load=_machines[_online[i]]->GetLoad();
                    if(min_load>cur_load)
                    {
                        min_load=cur_load;
                        id=_online[i];
                        mp=_machines[id];
                    }
                }
                return true;
            }
            //下线主机
            void OfflineMachine(const int& id)
            {
                std::unique_lock<std::mutex> lock(_mutex);

                for(auto it=_online.begin();it!=_online.end();it++)
                {
                    if(*it==id)
                    {
                        _machines[id]->SetLoad(0);//下线，清除负载
                        _online.erase(it);
                        _offline.push_back(id);
                        break;
                    }
                }
            }
            //上线主机
            void OnlineMachine()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                _online.insert(_online.end(), _offline.begin(), _offline.end());
                _offline.erase(_offline.begin(), _offline.end());
                LOG(DEBUG,"所有主机已上线！\n");
            }

            //输出所有主机的id
            void ShowMachines()
            {
                std::unique_lock<std::mutex> lock(_mutex);

                std::cout<<"当前在线主机id：";
                for(const auto& id:_online)
                {
                    std::cout<<id<<" ";
                }
                std::cout<<std::endl;
                std::cout<<"当前离线主机id：";
                for(const auto& id:_offline)
                {
                    std::cout<<id<<" ";
                }
                std::cout<<std::endl;
            }

        private:
            std::vector<MachinePtr> _machines;//可提供服务的所有主机(每台主机都有自己的下标，用这个下标充当主机的id)
            std::vector<int> _online;//所有在线的主机id
            std::vector<int> _offline;//下线主机的id
            std::mutex _mutex;
        };

        //控制模块
        class Control
        {
        public:
            //上线所有主机
            void RecoveryMachines()
            {
                _load_blance.OnlineMachine();
            }
            //构建题目列表的HTML页面
            bool AllQuestionsHTML(std::string& html)
            {
                bool ret=true;
                std::vector<ns_Model::QuestionPtr> aq;
                if(_model.GetAllQuestions(aq))
                {
                    //把数据按题号排序
                    std::sort(aq.begin(),aq.end(),[](const ns_Model::QuestionPtr& q1,const ns_Model::QuestionPtr& q2){
                        return std::stoi(q1->number)<std::stoi(q2->number);
                    });
                    //将所有题目数据构建成网页
                    _view.AllQuestionsExpandHTML(aq,html);
                }
                else
                {
                    html="获取题目列表失败！";
                    ret=false;
                }
                return ret;
            }

            //构建指定题目的HTML页面
            bool QuestionHTML(const std::string& num,std::string& html)
            {
                bool ret=true;
                ns_Model::QuestionPtr q;
                if(_model.GetOneQuestion(num,q))
                {
                    //将指定题目数据构建成网页
                    _view.OneQuestionExpandHTML(q,html);
                }
                else
                {
                    html="获取题目:"+num+"失败！";
                    ret=false;
                }
                return ret;
            }
            //编译运行代码
            void Judge(const std::string& number,const std::string& in_json,std::string& out_json)
            {
                //根据题号获取题目信息
                ns_Model::QuestionPtr q;
                bool ret=_model.GetOneQuestion(number,q);
                if(!ret)
                {
                    return;
                }
                //对in_json进行反序列化，得到用户提交的代码及输入input(可有可无)
                Json::Value in_val;
                Json::Reader reader;
                reader.parse(in_json,in_val);
                std::string code=in_val["code"].asString();
                //拼接用户提交的代码和测试用例代码，形成完整的测试代码
                Json::Value compile_val;
                compile_val["input"]=in_val["input"].asString();
                compile_val["code"]=code+"\n"+q->tail;
                compile_val["cpu_limit"]=q->cpu_limit;
                compile_val["mem_limit"]=q->mem_limit;
                //对compile_val进行序列化，生成要发送给编译运行模块进行编译运行的Json字符串
                Json::FastWriter writer;
                std::string compile_str=writer.write(compile_val);
                //负载均衡的选择主机
                while(true)
                {
                    int id=0;
                    MachinePtr mp;
                    if(!_load_blance.SmartChoice(id,mp))
                    {
                        break;
                    }
                    //发送http请求，获取结果
                    //此时充当客户端给服务端发送编译运行请求，并获取结果
                    httplib::Client client(mp->GetIP(),mp->GetPort());
                    mp->LoadIncrease();//主机负载增加
                    LOG(INFO,"主机：%d 被选择,主机信息:[%s:%d],主机负载：%d\n",id,mp->GetIP().c_str(),mp->GetPort(),mp->GetLoad());
                    if(auto res=client.Post("/compile_server",compile_str,"application/json;charset=utf-8"))
                    {
                        //http请求完成
                        if(res->status==200)
                        {
                            //本次http请求成功
                            //获取结果
                            out_json=res->body;
                            mp->LoadDecrease();
                            LOG(INFO,"请求编译运行服务成功！\n");
                            break;
                        }
                        mp->LoadDecrease();
                    }
                    else
                    {
                        //http请求失败
                        LOG(ERROR,"主机：%d状态异常\n",id);
                        mp->LoadDecrease();
                        _load_blance.OfflineMachine(id);
                        _load_blance.ShowMachines();
                    }
                    //_load_blance.ShowMachines();
                    //sleep(1);
                }
                
            }

        private:
            ns_Model::Model _model;//负责数据交互
            ns_View::View _view;//负责渲染页面
            LoadBlance _load_blance;//负责负载均衡
        };
    }
}